#include "MShell.hpp"
#include "json/json.h"

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

static const int RECV_TOTAL_MAXSIZE = 409600;

static const int RECV_GROUP_MAXSIZE = 1400;
static const int SEND_GROUP_MAXSIZE = 450;

static int PORT;
static string REMOTE_IP = "114.116.233.27";  //"127.0.0.1"

/*
 * read three files and merge them
 * para: string fn1, string fn2, string fn3, they are all filenames
 * return pair<bool, string>: code true with recv content, false with error content
 */
pair<bool, string> mergeThreeFile(string fn1, string fn2, string fn3){
    pair<bool, string> fnret1 = read_file_to_string(fn1);
    pair<bool, string> fnret2 = read_file_to_string(fn2);
    pair<bool, string> fnret3 = read_file_to_string(fn3);
    if (!fnret1.first || !fnret2.first || !fnret3.first){
        return {false, fnret1.second + " and " + fnret2.second + " and " + fnret3.second};
    }
    else{
        return {true, fnret1.second + fnret2.second + fnret3.second};
    }
}

/*
 * parse data for sending
 * para: const unordered_map<string, string>& um
 * return: string
 *
 * this function execs: unordered_map<string, string>->JSON string
 */
string parseSendData(const unordered_map<string, string>& um){
    Json::Value v;
    
    for (const auto& iterum:um) v[iterum.first] = iterum.second;
    
    Json::StreamWriterBuilder sw;
    sw.settings_["indentation"] = "";
    std::ostringstream osStream;
    string resStr;

    Json::StreamWriter* psw = sw.newStreamWriter();
    psw->write(v, &osStream);
    return osStream.str();
}

/*
 * parse data of recving
 * in default, it is an unordered_map
 * para: const string& raw, Json::Value& root
 * return pair<bool, Json::Value>: code true with none, false with error string
 *
 * note: set value of root inside the function
 */
pair<bool, string> parseRecvData(const string& raw, Json::Value& root){
    JSONCPP_STRING errs;
    Json::CharReaderBuilder readerBuilder;

    std::unique_ptr<Json::CharReader> const jsonReader(readerBuilder.newCharReader());
    bool res;
    res = jsonReader->parse(raw.c_str(), raw.c_str() + raw.length(), &root, &errs);
    if (!res || !errs.empty()) return {false, (string)errs};
    else return {true, ""};
}

/*
 * recv data, with recv() and send() inside, recv grouped data
 * para: int connfd
 * return pair<bool, string>: code true with recv content, false with error content
 */
pair<bool, string> recvData(int connfd){
    string res;
    while(true){
        char buff[RECV_GROUP_MAXSIZE + 1];
        memset(buff, 0, sizeof(char)*(RECV_GROUP_MAXSIZE + 1));
    recvData_innerstart:
        int n = recv(connfd, buff, RECV_GROUP_MAXSIZE, 0);
        if (n < 0){
            if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {    //not fatal error
                cout << "recvData group step, recv interrupt: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                goto recvData_innerstart;
            }
            else{
                cout << "recvData group step, recv error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                return {false, ""};
            }
        }
        else{
            buff[n] = '\0';
            string recvGroupRecvdata = string(buff);
            if (n == RECV_GROUP_MAXSIZE){
                cout << "recvData group step, recv error: data too long!, data is:" << recvGroupRecvdata << endl;
                return {false, ""};
            }
            Json::Value rGR_v;
            pair<bool, string> rGR_ParseRet = parseRecvData(recvGroupRecvdata, rGR_v);
            if (!rGR_ParseRet.first || !rGR_v.isObject() || !rGR_v.isMember("t") || !rGR_v["t"].isString() ||
                !((rGR_v["t"].asString() == "0") || (rGR_v["t"].asString() == "1")) ||
                !rGR_v.isMember("l") || !rGR_v["l"].isString() || !rGR_v.isMember("d") || !rGR_v["d"].isString()){
                cout << "recvData group step, recv error: data format invalid, data is:" << recvGroupRecvdata << endl;
                return {false, ""};
            }
            else{
                int rGR_len;
                try{
                    rGR_len = stoi(rGR_v["l"].asString());
                }
                catch(...){
                    cout << "recvData group step, recv error: data format invalid, data is:" << recvGroupRecvdata << endl;
                    return {false, ""};
                }
                string realRecvStr = rGR_v["d"].asString();
                
                if (rGR_len != (int)realRecvStr.size()){
                    cout << "recvData group step, recv error: data format invalid, len check error, data is:" << recvGroupRecvdata << endl;
                    return {false, ""};
                }
                else{
                    res.append(realRecvStr.begin(), realRecvStr.end());
                    if (rGR_v["t"] == "1") break;
                    else if (res.size() > RECV_TOTAL_MAXSIZE){
                        cout << "recvData group step, recv error: data too long, data size is:" << recvGroupRecvdata.size() << endl;
                        return {false, ""};
                    }
                    else{
						cout << "recvData group step, recv ok: size is:" << n << endl;
                        //send 0(get group data ok) back
                        string respGroupSenddata = "{\"t\":\"0\"}";
                        if( send(connfd, respGroupSenddata.c_str(), respGroupSenddata.size(), 0) < 0){
                            cout << "recvData group step, send res data back error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                            return {false, ""};
                        }
                        continue;
                    }
                }
            }
        }
    }
	cout << endl;
    cout << "recvData: total size is:" << res.size() << endl;
    //cout << "recvData: recv msg from client:" << res << endl;
    return pair<bool, string>{true, res};
}

/*
 * parse and send data, with send() and recv() inside, send grouped data
 * para: const string& sendMsg, int connfd
 * return true if send ok else error
 */
bool sendData(const string& sendMsg, int connfd)
{
    int sMSize = sendMsg.size();
    cout << "LOG::client: send data total size:" << sMSize << endl;
    
    //拆分发送
    int sendIndex = 0;
    bool isLastGroup = false;
    while(sendIndex < sMSize){
        //gen send data1
        unordered_map<string, string> groupum;
        if ((sendIndex + SEND_GROUP_MAXSIZE) < sMSize){
            groupum["t"] = "0";
            groupum["l"] = to_string(SEND_GROUP_MAXSIZE);
            groupum["d"] = sendMsg.substr(sendIndex, SEND_GROUP_MAXSIZE);
        }
        else{
			isLastGroup = true;
            groupum["t"] = "1";
            groupum["l"] = to_string(sMSize - sendIndex);
            groupum["d"] = sendMsg.substr(sendIndex, sMSize - sendIndex);
        }
        //parse send data
        string groupStr = parseSendData(groupum);

        if( send(connfd, groupStr.c_str(), strlen(groupStr.c_str()), 0) < 0){
            cout << "sendData group step, send error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
            return false;
        }
        else{
			if (isLastGroup){
				cout << "sendData group has sent all data" << endl;
				break;
			}
			
			//recv data, check it
			cout << "sendData group send one ok, size:" << " (" << groupum["l"] << "," << groupStr.size() << ") " << endl;
		
            char buff[RECV_GROUP_MAXSIZE + 1];
            memset(buff, 0, sizeof(char)*(RECV_GROUP_MAXSIZE + 1));
        sendData_innerstart:
            int n = recv(connfd, buff, RECV_GROUP_MAXSIZE, 0);
            if (n < 0){
                if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {//not fatal error
                    cout << "sendData group step, recv interrupt: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                    goto sendData_innerstart;
                }
                else{
                    cout << "sendData group step, recv error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                    return false;
                }
            }
            else{
                buff[n] = '\0';
                string sendGroupRecvdata = string(buff);
                if (n == RECV_GROUP_MAXSIZE){
                    cout << "sendData group step, recv error: data too long!, data is:" << sendGroupRecvdata << endl;
                    return false;
                }
                Json::Value sGR_v;
                pair<bool, string> sGR_ParseRet = parseRecvData(sendGroupRecvdata, sGR_v);
                if (!sGR_ParseRet.first || !sGR_v.isObject() || !sGR_v.isMember("t") || !sGR_v["t"].isString() ||
                    sGR_v["t"].asString() != "0"
                    ){
                    cout << "sendData group step, recv error: data format invalid or server rep:false, data is:" << sendGroupRecvdata << endl;
                    return false;
                }
                else{
					cout << "sendData group recv one resp ok, ";
				    cout << "sendData group has some remaining data to send" << endl;
                    sendIndex += SEND_GROUP_MAXSIZE;
                    continue;
                }
            }
        }
    }
	cout << endl;
    return true;
}

void testRunOneTestCase(int fileID)
{
    int sockfd;
    struct sockaddr_in  servaddr;

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        cout << "create socket error:" << strerror(errno) << "(errno: " << errno << ")" << endl;
        return;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    //if( inet_pton(AF_INET, "39.97.243.168", &servaddr.sin_addr) <= 0){
    //  cout << "inet_pton error for 39.97.243.168" << endl;
    //    return;
    //}
    
    if( inet_pton(AF_INET, REMOTE_IP.c_str(), &servaddr.sin_addr) <= 0){
        cout << "inet_pton error: " << REMOTE_IP << endl;
        return;
    }

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        cout << "connect error:" << strerror(errno) << "(errno: " << errno << ")" << endl;
        return;
    }

    unordered_map<string, string> sendum;
    cout << "send msg to server: YOU should enter code \n 1:get cpp testcases \n 2:save cpp testcases\n";
    cout << "3:get js testcases \n 4:save js testcases\n";
    cout << "9: run cus testcase of BE(builderror)\n";
    cout << "10: run cus testcase of MLE\n 11: run cus testcase of TLE\n 12: run cus testcase of OF\n 13: run cus testcase of RE\n";
    cout << "14: run cus testcase of AC\n 15: run many testcases of AC";
    
    cout << endl;
    string code;
    cin >> code;
    
    if (code == "1"){
        sendum["code"] = "1";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
    }
    else if (code == "2"){
        pair<bool, string> inputReadRet = read_file_to_string("libs/inputSubmit.txt");
        pair<bool, string> outputReadRet = read_file_to_string("libs/outputSubmit.txt");
        if (!inputReadRet.first || !outputReadRet.first){
            cout << "inputSubmit or outputSubmit not found" << endl;
            return;
        }
        sendum["code"] = "2";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = inputReadRet.second;
        sendum["output"] = outputReadRet.second;
    }
    else if (code == "3"){
        sendum["code"] = "1";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "js";
    }
    else if (code == "4"){
        pair<bool, string> inputReadRet = read_file_to_string("libs/inputSubmit.txt");
        pair<bool, string> outputReadRet = read_file_to_string("libs/outputSubmit.txt");
        if (!inputReadRet.first || !outputReadRet.first){
            cout << "inputSubmit or outputSubmit not found" << endl;
            return;
        }
        sendum["code"] = "2";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "js";
        sendum["input"] = inputReadRet.second;
        sendum["output"] = outputReadRet.second;
    }
    else if (code == "9"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_BE_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/BEtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "10"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_MLE";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/MLEtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "11"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_TLE_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/TLEtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "12"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_OF_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/OFtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "13"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_RE_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/REtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "14"){
        sendum["code"] = "10";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["input"] = "\"bfasufiasfnjalksblkb\"";
        sendum["submitID"] = "test_AC_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/ACtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else if (code == "15"){
        sendum["code"] = "11";
        sendum["dir"] = "testBlock/testProblem1_1";
        sendum["language"] = "cpp";
        sendum["submitID"] = "test_AC_submit_";
        sendum["maxtime"] = "2";
        sendum["maxmemory"] = "20";
        pair<bool, string> usercodeReadRet = mergeThreeFile("libs/testinclude.hpp", "libs/ACtest.cpp", "libs/runtest.cpp");
        if (!usercodeReadRet.first){
            cout << "some source file not found" << endl;
            return;
        }
        sendum["usercode"] = usercodeReadRet.second;
    }
    else{
        cout << "enter code not found" << endl;
        return;
    }
    
    string sendStr = parseSendData(sendum);
    if (sendData(sendStr, sockfd)){
        pair<bool, string> recvRet = recvData(sockfd);
        if (!recvRet.first){
            cout << "recv data failed" << recvRet.second << endl;
        }
        Json::Value root;
        pair<bool, string> parseRet = parseRecvData(recvRet.second, root);
        if (!parseRet.first){
            cout << "parse recv data failed" << parseRet.second << endl;
        }
        cout << "recv data ok, data is:" << recvRet.second << endl;
    }else{
        cout << "send data failed" << endl;
    }
    
    close(sockfd);
}

int main(int argc, char** argv){
    cout << "enter port:" << endl;
    cin >> PORT;
    for (int i = 0; i < 10; ++i) testRunOneTestCase(i);
    return 0;
}
